package cz.gelion.jpqlw;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;


@WebServlet("/jpqlw")
public class QueryServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
	Logger log  = Logger.getLogger(QueryServlet.class);
	EntityManagerFactory factory;
	
	String SESSION_OBJECT_MAP = QueryServlet.class.getName() + ".objectMap";
	String QUERY_LIST = QueryServlet.class.getName() + ".queryList";
	String TRANSACTION = QueryServlet.class.getName() + ".transaction";
	
	
	protected void readResource(String resource, PrintWriter out) throws IOException {
		InputStream is = this.getClass().getResourceAsStream(resource);
		LineNumberReader r = new LineNumberReader(new InputStreamReader(is));
		String s;
		while ((s = r.readLine()) != null) {
			out.println(s);
		}
		r.close();
		is.close();
	}
	
	protected List<String> getQueryList(HttpServletRequest req) {
		List<String> l = (List<String>)req.getSession().getAttribute(QUERY_LIST);
		if (l == null) {
			req.getSession().setAttribute(QUERY_LIST, l = new ArrayList<String>());
		}
		return l;
	}
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		log.debug("doGet(" + req + "," + resp + ")");
		
		PrintWriter out = resp.getWriter();
		String s;
		resp.setContentType("text/html");
		if (req.getParameter(".css") != null) {
			log.debug("### sending styles");
			resp.setContentType("text/css");
			readResource("QueryServlet.css", out);
			out.flush();
			return;
		}
		
		
		if(req.getParameter(".js") != null) {
			log.debug("### sending javascript");
			resp.setContentType("text/javascript");
			readResource("QueryServlet.js", out);
			out.flush();
			return;
		}
		
		
		printHeader(out);
		if ((s = req.getParameter("object")) != null && (s = s.trim()).length() > 0) {
			printObjectInfo(out, getFromObjectMap(req,Integer.parseInt(s)));
		} else {
			printForm(out, "", getQueryList(req));
		}
		printFooter(out);
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		log.debug("doPost(" + req + "," + resp + ")");
		
		resp.setContentType("text/html");
		
		PrintWriter out = resp.getWriter();
		
		printHeader(out);
		
		EntityTransaction tr = (EntityTransaction)req.getSession().getAttribute(TRANSACTION);
		log.debug("### transaction: " + tr); 
		 
		
		
		
		String query = ((query = req.getParameter("query")) == null) ? "" : query.trim();
		if (query.length() > 0) {
			getQueryList(req).add(0, query);
		}
		printForm(out, query, getQueryList(req));
		
		
		String s;
		if ((s = req.getParameter(".transaction")) != null && tr != null) {
			log.debug("### trasaction: " + s);
			out.print("<div class='trasactionMessage'>Transaction ");
			if ("commit".equalsIgnoreCase(s)) {
				tr.commit();
				out.print("commited");
			} else {
				tr.rollback();
				out.print("aborted");
			}
			out.println("</div>");
		}
	   
	    
		
		if (query.length() > 0) {
			
			try {

				EntityManager em = factory.createEntityManager();	            
			    Query q = em.createQuery(query);
			    
			    getObjectMap(req).clear();
			
			    
			    try {
			    	log.debug("### trying update...");
			    	tr = em.getTransaction();
			    	req.getSession().setAttribute(TRANSACTION, tr);
			    	tr.begin();
			    	int i = q.executeUpdate();
			    	
			    	out.print("<div id='updateMessage' class='updateMessage'>");
			    	out.print(i + " items updated.");
			    	if (i > 0){
			    		out.println("<script>enableQueryForm(false);</script>");
			    		out.print("<button onClick='trasaction(\"commit\");'>Commit</button>");
			    		out.print("<button onClick='trasaction(\"rollback\");'>Rollback</button>");
			    	}
			    	out.println("</div>");
			    	log.debug("### ... updated " + i + " items");
			    	
			    } catch (IllegalStateException ex) {
			    	log.debug("### trying select ...");
			    	if (tr != null && tr.isActive()) {
			    		tr.rollback();
			    	}
			    	out.println("<div class='objectList'>");
			    	out.println("<ul>");
			    	Object[] line = null;
			    	for (Object o: q.getResultList()) {
			    		if (o.getClass().isArray()) {
			    			line = (Object[])o;
			    		} else {
			    			line = new Object[]{o};
			    		}
			    		
			    		
			    		out.print("<li>");
			    		for (int j=0; j<line.length; j++) {
			    			if (j>0) {
			    				out.print(", ");
			    			}
			    			putToObjectMap(req, line[j]);
			    			out.print("<a href='#' onClick=\"openObject('?object=");
			    			out.print(line[j].hashCode());
			    			out.print("');\">#</a>&#160;");
			    			String t = "class: " + ((line[j] != null) ? line[j].getClass().getCanonicalName() : "null");
			    			out.print("<span title='" + t + "' class='objectString'>" + line[j] + "</span>");
			    		}
			    		out.println("</li>");
			    	}
			    	out.println("</ul>");
			    	out.println("</div>");
			    } 

			    em.close();
			} catch (Exception e) {
				log.error(e.getMessage(), e);
				if (tr != null && tr.isActive()) {
					tr.rollback();
				}
				out.println("<div class='queryException'><pre>");
				
				e.printStackTrace(out);
				
				out.println("</pre></div>");
			}
			log.debug("### objectMap.size: " + getObjectMap(req).size());
			log.debug("### queryList:" + getQueryList(req));
		}
		
		printFooter(out);
		
	}

	
	protected void putToObjectMap(HttpServletRequest req, Object o) {
		if (o != null) {
			getObjectMap(req).put(o.hashCode(), o);
		}
	}
	
	protected Object getFromObjectMap(HttpServletRequest req, Integer i) {
		return getObjectMap(req).get(i);
	}
	
	protected Map<Integer, Object> getObjectMap(HttpServletRequest req) {
		Map<Integer, Object> map = (Map<Integer, Object>)req.getSession().getAttribute(SESSION_OBJECT_MAP);
		if (map == null) {
			req.getSession().setAttribute(SESSION_OBJECT_MAP, map = new HashMap<Integer, Object>());
		}
		return map;
	}
	
	protected void printForm(PrintWriter out, String content, List<String> history) {
		out.println("<div class='queryArea'>");
		out.println("<div class='queryForm'>");
		out.println("<form  name='queryForm' method='post'><textarea name='query' cols='46' rows='4'>");
		out.println(content);
		out.println("</textarea>");
		out.println("<button id='submitQueryButton' onClick='this.form.submit();return true;'> >> </button>");
		out.println("</form>");
		out.println("</div>");
		out.println("<div class='queryHistory'>");
		for (String s: history) {
			out.println("<div onClick='putToQuery(this);' class='item'>" + s + "</div>");
		}
		out.println("</div>");
		out.println("</div>");
	}
	
	
	protected void printHeader(PrintWriter out) {
		out.println("<html>");
		out.println("<head>");
		out.println("<script src='?.js=1'>" + "/* */" + "</script>");
		out.println("<link rel='stylesheet' type='text/css' href='?.css=1'/>");
		out.println("<title>JPAQuery</title></head><body>");
		out.println("<form method='post' name='transactionForm'><input type='hidden' name='.transaction' value='rollback'/></form>");
	}
	
	protected void printFooter(PrintWriter out) {
		out.println("</body></html>");
	}
	
	protected void printObjectInfo(PrintWriter out, Object o) {
		Class<? extends Object> clazz = o.getClass();
		EntityManager em  = null;
		try {
			o = (em = factory.createEntityManager()).merge(o);
			
	        out.println("<div class='objectDescription'>");
			out.println("<div class='className'>" + o + "</div>");
			BeanInfo info = Introspector.getBeanInfo(clazz);
			out.println("<ul>");
			for (PropertyDescriptor pd: info.getPropertyDescriptors()) {
				out.print("<li>");
					out.print("<span class='propertyName'>" + pd.getName() + "</span>");
					out.print("=");
					Method m = pd.getReadMethod();
					Object n = m.invoke(o, new Object[]{});
					String t = "class (declared/runtime): " + m.getReturnType().getCanonicalName() + "/" + ((n != null) ? n.getClass().getCanonicalName() : "null");
					out.print("<span title='" + t + "' class='propertyValue'>" + n + "</span>");
				out.println("</li>");
			}
			out.println("</ul>");
			out.println("</div>");
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			e.printStackTrace();
		} finally {
			if (em != null && em.isOpen()) {
				em.close();
			}
		}
		
	}
	
	@Override
	public void destroy() {
		super.destroy();
	}

	
	
	@Override
	public void init(ServletConfig cfg) throws ServletException {
		log.debug("init(" + cfg  + ")");
		Properties p = new Properties();
		
		Enumeration<String> e = cfg.getInitParameterNames();
		
		while (e.hasMoreElements()) {
			Object o;
			p.put(o = e.nextElement(), cfg.getInitParameter((String)o));
		}
		
		factory  = Persistence.createEntityManagerFactory(p.getProperty("persistence.unit"), p);
		
		
		super.init(cfg);
	}


	

}
